/*
 *   SlimPlayer Copyright (c) 2006 Koelec
 *
 *   This file is part of SlimPlayer plugin for SageTv.
 *
 *   Slimplayer is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   Slimplayer is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with SoftSqueeze; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

package nl.cmkprojects.sage.slimplayer.squeeze;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;

public class CliMessage
{
  private static final String globalCommands[] = new String[]
  { "login", "debug", "listen", "pref", "exit", "player", "rescan",
      "wipecache", "info", "genres", "artists", "albums", "songinfo", "titles",
      "songs", "playlists", "mixer", "path", "remote", "playlist", 
      "favorites", "artist", "current_title", "mode", "displaynow", "remote", "power", "title" };

  private char msg[];

  private StringTokenizer stringTok;

  private int idx;

  private String playerid;

  private String command;

  private ArrayList parameters = new ArrayList();

  /**
   * Create a new global cli message.
   * 
   * @param command
   */
  public CliMessage(String command)
  {
    this(null, command);
  }

  /**
   * Create a player specific cli message.
   * 
   * @param playerid
   * @param command
   */
  public CliMessage(String playerid, String command)
  {
    if (command == null)
      throw new IllegalArgumentException("Command cannot be null");

    this.playerid = playerid;
    this.command = command;
  }

  private CliMessage(char[] msg)
  {
    this.msg = msg;
    stringTok = new StringTokenizer(new String(msg), " ");

    String param = parseNextParameter();
    playerid = param;
    if (playerid != null)
    {
      for (int i = 0; i < globalCommands.length; i++)
      {
        if (playerid.equals(globalCommands[i]))
        {
          command = playerid;
          playerid = null;
          break;
        }
      }
    }
    if (command == null)
    {
      param = parseNextParameter();
      command = param;
    }
    while (param != null)
    {
      param = parseNextParameter();
      if (param != null)
      {
        parameters.add(param);
      }
    }
  }

  public static CliMessage parseMessage(String msg)
  {
    return new CliMessage(msg.toCharArray());
  }

  /**
   * Add a parameter to the cli message.
   * 
   * @param parameter
   * @return
   */
  public CliMessage addParameter(String parameter)
  {
    parameters.add(parameter);
    return this;
  }

  /**
   * Add a tag value pair to the cli message.
   * 
   * @param tag
   * @param value
   * @return
   */
  public CliMessage addParameter(String tag, String value)
  {
    parameters.add(tag + ":" + value);
    return this;
  }

  /**
   * Return the command for the cli message.
   * 
   * @return
   */
  public String getCommand()
  {
    return command;
  }

  /**
   * Return the playerid, if any, for this cli message.
   * 
   * @return
   */
  public String getPlayerid()
  {
    return playerid;
  }

  /**
   * Return an iterator over the parameters in this cli message.
   * 
   * @return
   */
  public CliParameterIterator getParameterIterator()
  {
    return new CliParameterIterator(parameters);
  }

  /**
   * Return the cli message as a string (ready to send to the slimserver).
   */
  public String toString()
  {
    StringBuffer buf = new StringBuffer();

    if (playerid != null)
    {
      urlEncode(buf, playerid);
      buf.append(" ");
    }

    buf.append(command);

    for (Iterator i = parameters.iterator(); i.hasNext();)
    {
      String str = (String) i.next();

      buf.append(" ");
      urlEncode(buf, str);
    }

    return buf.toString();
  }

  /*
   * (non-Javadoc)
   * 
   * @see java.lang.Object#equals(java.lang.Object)
   */
  public boolean equals(Object obj)
  {
    if (!(obj instanceof CliMessage))
      return false;

    CliMessage msg = (CliMessage) obj;

    if (playerid == null)
      return msg.playerid == null && command.equals(msg.command);
    else
      return playerid.equals(msg.playerid) && command.equals(msg.command);
  }

  /*
   * (non-Javadoc)
   * 
   * @see java.lang.Object#hashCode()
   */
  public int hashCode()
  {
    return command.hashCode();
  }

  private static void urlEncode(StringBuffer buf, String s)
  {
    char c[] = s.toCharArray();

    for (int i = 0; i < c.length; i++)
    {
      if (c[i] <= 31 || c[i] >= 0x7F)
      {
        buf.append('%');
        buf.append(Integer.toString((int) c[i], 16).toUpperCase());
      } else
      {
        switch (c[i])
        {
        // Reserverd characters
        case '$':
        case '&':
        case '+':
        case ',':
        case '/':
        case ':':
        case ';':
        case '=':
        case '?':
        case '@':

        // Unsafe characters
        case ' ':
        case '"':
        case '<':
        case '>':
        case '#':
        case '%':
        case '{':
        case '}':
        case '|':
        case '\\':
        case '^':
        case '~':
        case '[':
        case ']':
        case '`':
          buf.append('%');
          buf.append(Integer.toString((int) c[i], 16).toUpperCase());
          break;

        default:
          buf.append(c[i]);
        }
      }
    }
  }

  private String parseNextParameter()
  {
    if (stringTok.hasMoreTokens())
    {
      String param = stringTok.nextToken();
      try
      {
        if (param.indexOf("RadioGuide") > 0)
        {
          String param2 = "";
        }
        // fix bug in SC's CLI
        param = param.replaceAll("%83%C2", "");
        return URLDecoder.decode(param, "UTF-8");
      } catch (UnsupportedEncodingException e)
      {
        e.printStackTrace();
        return param;
      }
    }
    return null;
  }

  private static byte convertHexDigit(char b)
  {
    if ((b >= '0') && (b <= '9'))
    {
      return (byte) (b - '0');
    }
    if ((b >= 'a') && (b <= 'f'))
    {
      return (byte) (b - 'a' + 10);
    }
    if ((b >= 'A') && (b <= 'F'))
    {
      return (byte) (b - 'A' + 10);
    }
    return 0;
  }
}
